/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>

#include <net/if.h>
#include <net/if_arp.h>
#include <gdbus.h>

#include <connman/assert.h>

#include "connman.h"

/* XXX hack because including <linux/if.h> does not work */
#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP	0x10000
#endif

/*
 * Prefixes are between 0 and PREFIXLEN_MAX bits.
 * For now the max is 32 (sufficient for IPv4); need this
 * to grow to 128 for IPv6.
 *
 * TODO(sleffler) ipv6 prefixes can be up to 128
 */
#if 0
#define	MAX_PREFIXLEN	128
#else
#define	MAX_PREFIXLEN	32
#endif

#define	_DBG_IPCONFIG(fmt, arg...)	DBG(DBG_INET, fmt, ## arg)

static DBusConnection *connection = NULL;

struct iproute {
	in_addr_t addr;
	in_addr_t netmask;
	in_addr_t gw;
};

struct connman_ipconfig {
	struct connman_element element;		/* base class */
	struct connman_ipaddress *address;	/* address state */
	void *tag;				/* associated object */
	struct connman_element *connection;	/* associated connection obj */
	struct connman_resolver_state *resolv;	/* associated resolver state */
	GSList *route_list;			/* associated iproutes */
	const struct connman_ipconfig_driver *driver; /* supporting driver */
};
static GList *ipconfig_list = NULL;		/* list of all ipconfig's */

struct connman_ipdevice {
	int index;
	char *ifname;
	unsigned short type;
	unsigned int flags;

	GSList *address_list;
	char *gateway;
};
static GHashTable *ipdevice_hash = NULL;	/* ipdevice hash by index */

static void ipdevice_free(gpointer data);
static void ipdevice_free_address_list(struct connman_ipdevice *ipdevice);
static void ipaddress_clear(struct connman_ipaddress *dst, int mask);

static const struct connman_ipconfig_driver *ipconfig_driver_lookup(
    enum connman_ipconfig_type);

/*
 * IP Address support.
 */

static struct connman_ipaddress *ipaddress_alloc(void)
{
	return g_try_new0(struct connman_ipaddress, 1);
}

static struct connman_ipaddress *ipaddress_new(int af, unsigned char prefixlen,
    const char *local)
{
	struct connman_ipaddress *ipaddress;

	ipaddress = ipaddress_alloc();
	if (ipaddress != NULL) {
		ipaddress->af = af;
		ipaddress->prefixlen = prefixlen;
		ipaddress->local = g_strdup(local);
		ipaddress->mask |= CONNMAN_IPCONFIG_AF
				|  CONNMAN_IPCONFIG_PREFIX
				|  CONNMAN_IPCONFIG_LOCAL
				;
	}
	return ipaddress;
}

static void ipaddress_free(struct connman_ipaddress *ipaddress)
{
	if (ipaddress == NULL)			/* TODO(sleffler) needed? */
		return;

	ipaddress_clear(ipaddress, CONNMAN_IPCONFIG_ALL);
	g_free(ipaddress);
}

static void ipaddress_set(struct connman_ipaddress *dst,
    const struct connman_ipaddress *src, int mask)
{
	if (dst == NULL || src == NULL)		/* XXX */
		return;

	CONNMAN_ASSERT(dst != src);

	if (mask & CONNMAN_IPCONFIG_AF)
		dst->af = src->af;
	if (mask & CONNMAN_IPCONFIG_MTU)
		dst->mtu = src->mtu;
	if (mask & CONNMAN_IPCONFIG_PREFIX)
		dst->prefixlen = src->prefixlen;
	if (mask & CONNMAN_IPCONFIG_LOCAL) {
		g_free(dst->local);
		dst->local = g_strdup(src->local);
	}
	if (mask & CONNMAN_IPCONFIG_PEER) {
		g_free(dst->peer);
		dst->peer = g_strdup(src->peer);
	}
	if (mask & CONNMAN_IPCONFIG_BCAST) {
		g_free(dst->broadcast);
		dst->broadcast = g_strdup(src->broadcast);
	}
	if (mask & CONNMAN_IPCONFIG_GW) {
		g_free(dst->gateway);
		dst->gateway = g_strdup(src->gateway);
	}
	if (mask & CONNMAN_IPCONFIG_DOMAIN) {
		g_free(dst->domain_name);
		dst->domain_name = g_strdup(src->domain_name);
	}
	if (mask & CONNMAN_IPCONFIG_DNS) {
		g_strfreev(dst->dns_servers);
		dst->dns_servers = g_strdupv(src->dns_servers);
	}
	if (mask & CONNMAN_IPCONFIG_SEARCH) {
		g_strfreev(dst->search_domains);
		dst->search_domains = g_strdupv(src->search_domains);
	}
	dst->mask = (dst->mask &~ mask) | (src->mask & mask);
}

static void ipaddress_clear(struct connman_ipaddress *dst, int mask)
{
	if (mask & CONNMAN_IPCONFIG_LOCAL) {
		g_free(dst->local);
		dst->local = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_PEER) {
		g_free(dst->peer);
		dst->peer = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_BCAST) {
		g_free(dst->broadcast);
		dst->broadcast = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_GW) {
		g_free(dst->gateway);
		dst->gateway = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_DOMAIN) {
		g_free(dst->domain_name);
		dst->domain_name = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_DNS) {
		g_strfreev(dst->dns_servers);
		dst->dns_servers = NULL;
	}
	if (mask & CONNMAN_IPCONFIG_SEARCH) {
		g_strfreev(dst->search_domains);
		dst->search_domains = NULL;
	}
	dst->mask &= ~mask;
}

/*
 * IP device support.
 */

/*
 * Create a new ipdevice of specifed type.  The device's
 * index and name are recorded and the instance is added
 * to the hash.
 */
static struct connman_ipdevice *ipdevice_new(int index, int type,
    const char *ifname, unsigned int flags)
{
	struct connman_ipdevice *ipdevice;

	ipdevice = g_try_new0(struct connman_ipdevice, 1);
	if (ipdevice != NULL) {
		ipdevice->index = index;
		ipdevice->ifname = g_strdup(ifname);
		ipdevice->type = type;
		ipdevice->flags = flags;

		g_hash_table_insert(ipdevice_hash, GINT_TO_POINTER(index),
		    ipdevice);
	}
	return ipdevice;
}

/*
 * Reclaim storage associated with an ipdevice.  Called
 * implicitly when an entry is removed from the hash table.
 */
static void ipdevice_free(gpointer data)
{
	struct connman_ipdevice *ipdevice = data;

	ipdevice_free_address_list(ipdevice);
	g_free(ipdevice->gateway);

	g_free(ipdevice->ifname);
	g_free(ipdevice);
}

/*
 * Remove an ipdevice entry.  The hash entry is removed
 * which causes storage to be reclaimed by ipdevice_free.
 */
static void ipdevice_delete(struct connman_ipdevice *ipdevice)
{
	/* NB: ipdevice_free called on remove */
	g_hash_table_remove(ipdevice_hash, GINT_TO_POINTER(ipdevice->index));
}

/*
 * Lookup an ipdevice entry by the associated interface index.
 */
static struct connman_ipdevice *ipdevice_lookup(int index)
{
	return g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index));
}

/*
 * Add an address associated with an ipdevice.
 */
static connman_bool_t ipdevice_add_ipaddress(struct connman_ipdevice *ipdevice,
    int af, unsigned char prefixlen, const char *address)
{
	struct connman_ipaddress *ipaddr;

	/* TODO(sleffler) check for/reject duplicate */
	ipaddr = ipaddress_new(af, prefixlen, address);
	if (ipaddr == NULL) {
		connman_error("%s(%s): cannot allocate ipaddress",
		    __func__, ipdevice->ifname);
		return FALSE;
	}
	ipdevice->address_list = g_slist_append(ipdevice->address_list, ipaddr);
	return TRUE;
}

/*
 * Remove an address associated with an ipdevice.
 */
static connman_bool_t ipdevice_remove_ipaddress(
    struct connman_ipdevice *ipdevice, unsigned char prefixlen,
    const char *local)
{
	GSList *list;

	for (list = ipdevice->address_list; list != NULL; list = list->next) {
		struct connman_ipaddress *ipaddress = list->data;

		if (g_strcmp0(ipaddress->local, local) == 0 &&
		    ipaddress->prefixlen == prefixlen) {
			/* TODO(sleffler) use slist_delete_link? */
			ipdevice->address_list =
			    g_slist_remove(ipdevice->address_list, ipaddress);
			ipaddress_free(ipaddress);
			return TRUE;
		}
	}
	return FALSE;
}

/*
 * Clear the address list associated with an ipdevice.
 */
static void ipdevice_free_address_list(struct connman_ipdevice *ipdevice)
{
	GSList *list;

	for (list = ipdevice->address_list; list != NULL; list = list->next) {
		struct connman_ipaddress *ipaddress = list->data;

		ipaddress_free(ipaddress);
		list->data = NULL;
	}

	g_slist_free(ipdevice->address_list);
	ipdevice->address_list = NULL;
}

/*
 * Set the current gateway for an ipdevice.
 */
static void ipdevice_set_gateway(struct connman_ipdevice *ipdevice,
    const char *gateway)
{
	g_free(ipdevice->gateway);
	if (gateway == NULL) {
		ipdevice->gateway = NULL;
		return;
	}
	ipdevice->gateway = g_strdup(gateway);
	if (ipdevice->gateway == NULL) {
		connman_error("%s(%s): no memory for gw %s", __func__,
		    ipdevice->ifname, gateway);
		/* NB: no way to record */
	}
}

/*
 * RTNL msg handling support.
 */

static const char *type2str(unsigned short type)
{
	switch (type) {
	case ARPHRD_ETHER:	return "ETHER";
	case ARPHRD_LOOPBACK:	return "LOOPBACK";
	case ARPHRD_PPP:	return "PPP";
	case ARPHRD_NONE:	return "NONE";
	case ARPHRD_VOID:	return "VOID";
	}
	return "???";
}

static int is_type_interesting(unsigned short type)
{
	/* NB: does not include loopback device */
	return (type == ARPHRD_ETHER || type == ARPHRD_PPP ||
		type == ARPHRD_NONE);
}

/*
 * Process a NEWLINK event/msg.  If the associated device is
 * known an entry is created.  Otherwise the flags are used
 * to identify up/down state changes.
 */
static void ipdevice_newlink(void *user_data, int index, unsigned short type,
    const char *ifname, unsigned int flags, int change)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d type %d flags 0x%x ifname %s",
	    index, type, flags, ifname);

	if (!is_type_interesting(type))
		return;

	ipdevice = ipdevice_lookup(index);
	if (ipdevice == NULL) {
		if (__connman_device_isfiltered(ifname) == TRUE) {
			connman_info("Ignoring network interface %s (filtered)",
			    ifname);
			return;
		}

		ipdevice = ipdevice_new(index, type, ifname, flags);
		if (ipdevice == NULL) {
			connman_error("%s: cannot allocate ipdevice "
			    "(index %d type %d flags 0x%x ifname %s)", __func__,
			    index, type, flags, ifname);
			return;
		}
		connman_inet_set_ipv6_privacy(ifname);
		connman_info("%s {create} index %d type %d <%s>", ifname,
		    index, type, type2str(type));
	} else {
		ipdevice->flags = flags;
		if (g_strcmp0(ifname, ipdevice->ifname) != 0) {
			g_free(ipdevice->ifname);
			ipdevice->ifname = g_strdup(ifname);
		}
	}
}

/*
 * Process a DELLINK event/msg.  Mark the ipdevice down
 * and remove the entry from the hash table.
 */
static void ipdevice_dellink(void *user_data, int index, unsigned short type,
    const char *ifname, unsigned flags, int change)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d type %d", index, type);

	if (!is_type_interesting(type))
		return;

	ipdevice = ipdevice_lookup(index);
	if (ipdevice != NULL) {
		connman_info("%s {remove} index %d",
		    ipdevice->ifname, ipdevice->index);

		ipdevice_delete(ipdevice);
	}
}

/*
 * Process a NEWADDR event/msg.  Add an entry to the
 * address list and notify anyone interested.
 */
static void ipdevice_newaddr(void *user_data, int index, const char *label,
    int af, int prefixlen, const char *address)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d", index);

	ipdevice = ipdevice_lookup(index);
	if (ipdevice == NULL)			/* NB: not an error */
		return;

	if (ipdevice_add_ipaddress(ipdevice, af, prefixlen, address) == TRUE) {
		connman_info("%s {add} address %s/%u (af %u) label %s",
		    ipdevice->ifname, address, prefixlen, af, label);
	}
}

/*
 * Process a DELADDR event/msg.  Remove any entry from the
 * address list and notify anyone interested.
 */
static void ipdevice_deladdr(void *user_data, int index, const char *label,
    int af, int prefixlen, const char *address)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d", index);

	ipdevice = ipdevice_lookup(index);
	if (ipdevice == NULL)
		return;

	if (ipdevice_remove_ipaddress(ipdevice, prefixlen, address) == TRUE) {
		connman_info("%s {del} address %s/%u (af %u) label %s",
		    ipdevice->ifname, address, prefixlen, af, label);
	}
}

static const char *scope2str(unsigned char scope)
{
	switch (scope) {
	case 0:		return "UNIVERSE";
	case 253:	return "LINK";
	}
	return "???";
}

/*
 * Process a NEWROUTE event/msg. Set any default route.
 */
static void ipdevice_newroute(void *user_data, int index, int scope,
    const char *dst, const char *gateway)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d scope %d dst %s gateway %s",
	    index, scope, dst, gateway);

	ipdevice = ipdevice_lookup(index);
	if (ipdevice == NULL)
		return;

	if (scope == 0 && g_strcmp0(dst, "0.0.0.0") == 0) {
		ipdevice_set_gateway(ipdevice, gateway);

		connman_info("%s {add} route %s gw %s scope %u <%s>",
		    ipdevice->ifname, dst, gateway, scope, scope2str(scope));
	}
}

/*
 * Process a DELROUTE event/msg. Remove any default route.
 */
static void ipdevice_delroute(void *user_data, int index, int scope,
    const char *dst, const char *gateway)
{
	struct connman_ipdevice *ipdevice;

	_DBG_IPCONFIG("index %d", index);

	ipdevice = ipdevice_lookup(index);
	if (ipdevice == NULL)
		return;

	if (scope == 0 && g_strcmp0(dst, "0.0.0.0") == 0) {
		/* XXX check it's what we have recorded? */
		ipdevice_set_gateway(ipdevice, NULL);

		connman_info("%s {del} route %s gw %s scope %u <%s>",
		    ipdevice->ifname, dst, gateway, scope, scope2str(scope));
	}
}

static struct connman_rtnl ipdevice_rtnl = {
	RTNL_DEFINE("ipdevice", CONNMAN_RTNL_PRIORITY_HIGH),

	.newlink	= ipdevice_newlink,
	.dellink	= ipdevice_dellink,
	.newaddr	= ipdevice_newaddr,
	.deladdr	= ipdevice_deladdr,
	.newroute	= ipdevice_newroute,
	.delroute	= ipdevice_delroute,
};

static void ipdevice_init(void)
{
	ipdevice_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
	    NULL, ipdevice_free);
	connman_rtnl_register(&ipdevice_rtnl);
}

static void ipdevice_cleanup(void)
{
	connman_rtnl_unregister(&ipdevice_rtnl);
	g_hash_table_destroy(ipdevice_hash);
	ipdevice_hash = NULL;
}

/*
 * ipdevice iterator (used by rtnl support).
 */
void __connman_ipdevice_foreach(void (*function)(int index, void *arg),
    void *arg)
{
	GList *keys;

	keys = g_hash_table_get_keys(ipdevice_hash);
	if (keys != NULL) {
		GList *list = g_list_first(keys);
		for (; list != NULL; list = g_list_next(list))
			function(GPOINTER_TO_INT(list->data), arg);
		g_list_free(keys);
	}
}

/*
 * ipconfig iterator.
 */
void __connman_ipconfig_foreach(void *tag,
    void (*function)(struct connman_ipconfig *, void *arg), void *arg)
{
	GList *list = g_list_first(ipconfig_list);
	for (; list != NULL; list = g_list_next(list)) {
		struct connman_ipconfig *ipconfig = list->data;

		if (tag == NULL || ipconfig->tag == tag)
			function(ipconfig, arg);
	}
}

static void iproute_list_free(GSList **plist)
{
	g_slist_free(*plist);
	*plist = NULL;
}

static int iproute_add(GSList **plist, const char *addr, const char *netmask,
    const char *gw)
{
	struct iproute *route;

	route = g_try_new0(struct iproute, 1);
	if (route == NULL)
		return FALSE;

	route->addr = inet_addr(addr);
	if (netmask != NULL)
		route->netmask = inet_addr(netmask);
	route->gw = inet_addr(gw);

	*plist = g_slist_append(*plist, route);
	return TRUE;
}

static int iproute_install(int index, const struct iproute *route)
{
	if (route->netmask == 0 || route->netmask == 0xffffffff)
		return connman_inet_add_hostroute(index, route->addr,
		    route->gw);
	else
		return connman_inet_add_route(index, route->addr,
		    route->netmask, route->gw);
}

static connman_bool_t ipconfig_isstatic(const struct connman_ipconfig *ipconfig)
{
	return (ipconfig->element.type == CONNMAN_ELEMENT_TYPE_IPV4 ||
		ipconfig->element.type == CONNMAN_ELEMENT_TYPE_IPV6);
}

void connman_ipconfig_set_address(struct connman_ipconfig *ipconfig,
    const struct connman_ipaddress *ipaddr)
{
	struct connman_ipaddress *current = ipconfig->address;

	/*
	 * Copy non-static data to ipconfig.  If we are being passed our
	 * current ipconfig, this is a static config and it is incorrect
	 * and dangerous to call ipaddress_set.
	 */
	if (current != ipaddr)
		ipaddress_set(current, ipaddr, ipaddr->mask &~ current->smask);
	/*
	 * NB: static values are cleared from the current mask
	 * by ipconfig_clear_address so we must re-enable them.
	 */
	current->mask |= current->smask;

	/* propagate gateway to the device */
	if (current->mask & CONNMAN_IPCONFIG_GW) {
		int index = connman_ipconfig_get_index(ipconfig);
		struct connman_ipdevice *ipdevice = ipdevice_lookup(index);
		if (ipdevice != NULL)
			ipdevice_set_gateway(ipdevice, current->gateway);
	}
}

void connman_ipconfig_clear_address(struct connman_ipconfig *ipconfig)
{
	struct connman_ipaddress *ipaddr = ipconfig->address;

	/* clear gateway if we set it */
	if (ipaddr->mask & CONNMAN_IPCONFIG_GW) {
		int index = connman_ipconfig_get_index(ipconfig);
		struct connman_ipdevice *ipdevice = ipdevice_lookup(index);
		if (ipdevice != NULL)
			ipdevice_set_gateway(ipdevice, NULL);
	}
	/*
	 * Clear dynamic state from ipconfig.  We preserve the static
	 * values but clear the bitmask so if this address is passed
	 * to connman_inet_set_address nothing will be set.
	 *
	 * NB: this does not leak resources because ipaddress_free
	 *     uses CONNMAN_IPCONFIG_ALL as a mask to reclaim data
	 * NB: subsequent changes to static values are still recorded
	 *     in the profile because ipconfig_save honors smask and
	 *     not (the dynamic) mask
	 */
	ipaddress_clear(ipaddr, ipaddr->mask &~ ipaddr->smask);
	ipaddr->mask = 0;
}

/*
 * Move <a> to be <off> relative <b>.  Use 0 to move <a> before <b>
 * and 1 to move <a> after <b>.
 */
static connman_bool_t ipconfig_move(struct connman_ipconfig *a,
    struct connman_ipconfig *b, int off)
{
	gint bix;

	bix = g_list_index(ipconfig_list, b);
	if (bix == -1) {
		connman_error("%s: target not registered", __func__);
		return FALSE;
	}
	ipconfig_list = g_list_remove(ipconfig_list, a);
	ipconfig_list = g_list_insert(ipconfig_list, a, bix+off);
	return TRUE;
}

static struct connman_ipconfig *ipconfig_find(const char *path)
{
	GList *list = g_list_first(ipconfig_list);
	for (; list != NULL; list = g_list_next(list)) {
		struct connman_ipconfig *ipconfig = list->data;

		if (g_strcmp0(connman_ipconfig_get_path(ipconfig), path) == 0)
			return ipconfig;
	}
	return NULL;
}

const char *__connman_ipconfig_get_method(
    const struct connman_ipconfig *ipconfig)
{
	switch (ipconfig->element.type) {
	case CONNMAN_ELEMENT_TYPE_IPV4:
		return "ipv4";
	case CONNMAN_ELEMENT_TYPE_IPV6:
		return "ipv6";
	case CONNMAN_ELEMENT_TYPE_DHCP:
		return "dhcp";
	case CONNMAN_ELEMENT_TYPE_PPP:
		return "ppp";
	case CONNMAN_ELEMENT_TYPE_BOOTP:
		return "bootp";
	case CONNMAN_ELEMENT_TYPE_ZEROCONF:
		return "zeroconf";
	default:
		connman_error("%s: unexpected type %d", __func__,
		    ipconfig->element.type);
		return "???";
	}
}

unsigned short __connman_ipconfig_get_type(int index)
{
	struct connman_ipdevice *ipdevice;

	ipdevice = ipdevice_lookup(index);
	return (ipdevice == NULL ? ARPHRD_VOID : ipdevice->type);
}

const char *__connman_ipconfig_get_ifname(int index)
{
	struct connman_ipdevice *ipdevice;

	ipdevice = ipdevice_lookup(index);
	return (ipdevice == NULL ? NULL : ipdevice->ifname);
}

unsigned int __connman_ipconfig_get_flags(int index)
{
	struct connman_ipdevice *ipdevice;

	ipdevice = ipdevice_lookup(index);
	return (ipdevice == NULL ? 0 : ipdevice->flags);
}

const char *__connman_ipconfig_get_gateway(int index)
{
	struct connman_ipdevice *ipdevice;

	ipdevice = ipdevice_lookup(index);
	return (ipdevice == NULL ? NULL : ipdevice->gateway);
}

static void ipconfig_destruct(struct connman_element *element)
{
	struct connman_ipconfig *ipconfig = (struct connman_ipconfig *) element;

	ipconfig_list = g_list_remove(ipconfig_list, ipconfig);
	ipaddress_free(ipconfig->address);
	iproute_list_free(&ipconfig->route_list);
	/* NB: element free's storage */
}

static const char *method2str(enum connman_ipconfig_type method)
{
	switch (method) {
	case CONNMAN_IPCONFIG_TYPE_IPV4:
		return "ipv4";
	case CONNMAN_IPCONFIG_TYPE_IPV6:
		return "ipv6";
	case CONNMAN_IPCONFIG_TYPE_DHCP:
		return "dhcp";
	case CONNMAN_IPCONFIG_TYPE_PPP:
		return "ppp";
	case CONNMAN_IPCONFIG_TYPE_BOOTP:
		return "bootp";
	case CONNMAN_IPCONFIG_TYPE_ZEROCONF:
		return "zeroconf";
	}
	return NULL;
}

/*
 * Return the ipconfig type/method for the ascii equivalent;
 * used by d-bus methods.
 */
connman_bool_t __connman_ipconfig_parse_type(const char *str,
    enum connman_ipconfig_type *type)
{
	if (g_ascii_strcasecmp(str, "ipv4") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_IPV4;
	else if (g_ascii_strcasecmp(str, "ipv6") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_IPV6;
	else if (g_ascii_strcasecmp(str, "dhcp") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_DHCP;
	else if (g_ascii_strcasecmp(str, "ppp") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_PPP;
	else if (g_ascii_strcasecmp(str, "bootp") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_BOOTP;
	else if (g_ascii_strcasecmp(str, "zeroconf") == 0)
		*type = CONNMAN_IPCONFIG_TYPE_ZEROCONF;
	else
		return FALSE;
	return TRUE;
}

static enum connman_element_type
ipconfig_type_to_element_type(enum connman_ipconfig_type type)
{
	enum connman_element_type etype;

	switch (type) {
	case CONNMAN_IPCONFIG_TYPE_PPP:
		etype = CONNMAN_ELEMENT_TYPE_PPP;
		break;
	case CONNMAN_IPCONFIG_TYPE_IPV4:
		etype = CONNMAN_ELEMENT_TYPE_IPV4;
		break;
	case CONNMAN_IPCONFIG_TYPE_IPV6:
		etype = CONNMAN_ELEMENT_TYPE_IPV6;
		break;
	case CONNMAN_IPCONFIG_TYPE_DHCP:
		etype = CONNMAN_ELEMENT_TYPE_DHCP;
		break;
	case CONNMAN_IPCONFIG_TYPE_BOOTP:
		etype = CONNMAN_ELEMENT_TYPE_BOOTP;
		break;
	case CONNMAN_IPCONFIG_TYPE_ZEROCONF:
		etype = CONNMAN_ELEMENT_TYPE_ZEROCONF;
		break;
	default:
		connman_error("%s: invalid ipconfig type %d", __func__, type);
		etype = CONNMAN_ELEMENT_TYPE_UNKNOWN;
		break;
	}
	return etype;
}

/**
 * connman_ipconfig_create:
 *
 * Allocate a new ipconfig structure.
 *
 * Returns: a newly-allocated #connman_ipconfig structure
 */
struct connman_ipconfig *connman_ipconfig_create(
    enum connman_ipconfig_type type, const char *name, void *tag, int index,
    connman_bool_t save_empty)
{
	struct connman_ipconfig *ipconfig;
	struct connman_ipaddress *ipaddr;
	enum connman_element_type etype;
	int af;

	_DBG_IPCONFIG("type %s(%d) name %s tag %p index %d",
	    method2str(type), type, name, tag, index);

	if ((index < 0) && (type != CONNMAN_IPCONFIG_TYPE_PPP)) {
		connman_error("%s: invalid ipconfig index %d", __func__, index);
		return NULL;
	}

	etype = ipconfig_type_to_element_type(type);
	if (etype == CONNMAN_ELEMENT_TYPE_UNKNOWN) {
		connman_error("%s: invalid ipconfig type %d", __func__, type);
		return NULL;
	} else if (type == CONNMAN_IPCONFIG_TYPE_IPV6) {
		af = AF_INET6;
	} else {
		af = AF_INET;
	}

	ipconfig = g_try_new0(struct connman_ipconfig, 1);
	if (ipconfig == NULL) {
		connman_error("%s: no memory for ipconfig", __func__);
		return NULL;
	}
	ipaddr = ipaddress_alloc();
	if (ipaddr == NULL) {
		connman_error("%s: cannot allocate address", __func__);
		g_free(ipconfig);
		return NULL;
	}
	ipaddr->af = af;
	ipaddr->mask |= CONNMAN_IPCONFIG_AF;
	ipconfig->address = ipaddr;

	__connman_element_initialize(&ipconfig->element);
	ipconfig->element.type = etype;
	ipconfig->element.name = g_strdup((gchar *) name); /* XXX __DECONST */
	ipconfig->element.destruct = ipconfig_destruct;
	ipconfig->element.index = index;

	ipconfig->tag = tag;
	ipconfig->driver = ipconfig_driver_lookup(type);

	ipconfig_list = g_list_append(ipconfig_list, ipconfig);

	if (save_empty)
		__connman_profile_save_ipconfig(ipconfig);

	return ipconfig;
}

struct connman_ipconfig *connman_ipconfig_find(void * tag,
					       enum connman_ipconfig_type type)
{
	enum connman_element_type etype = ipconfig_type_to_element_type(type);
	GList *list;

	if (etype == CONNMAN_ELEMENT_TYPE_UNKNOWN)
		return NULL;

	for (list = g_list_first(ipconfig_list);
	     list != NULL;
	     list = g_list_next(list)) {
		struct connman_ipconfig *ipconfig = list->data;

		if (ipconfig->tag == tag && ipconfig->element.type == etype) {
			_DBG_IPCONFIG("found existing ipconfig type %s for tag %p",
			    method2str(type), tag);
			return ipconfig;
		}
	}
	return NULL;
}

/**
 * connman_ipconfig_delete:
 *
 * Delete an ipconfig structure.
 */
void connman_ipconfig_delete(struct connman_ipconfig *ipconfig)
{
	connman_element_unregister(&ipconfig->element);
	connman_ipconfig_unref(ipconfig);
}

/**
 * connman_ipconfig_get_data:
 * @network: network structure
 *
 * Get private ipconfig data pointer
 */
void *connman_ipconfig_get_data(const struct connman_ipconfig *ipconfig)
{
	return ipconfig->element.driver_data;
}

/**
 * connman_ipconfig_set_data:
 * @network: network structure
 * @data: data pointer
 *
 * Set private ipconfig data pointer
 */
void connman_ipconfig_set_data(struct connman_ipconfig *ipconfig, void *data)
{
	ipconfig->element.driver_data = data;
}

int connman_ipconfig_getaf(const struct connman_ipconfig *ipconfig)
{
	switch (ipconfig->element.type) {
	case CONNMAN_ELEMENT_TYPE_IPV4:
	case CONNMAN_ELEMENT_TYPE_DHCP:
	case CONNMAN_ELEMENT_TYPE_BOOTP:
	case CONNMAN_ELEMENT_TYPE_ZEROCONF:
	case CONNMAN_ELEMENT_TYPE_PPP:
		return AF_INET;
	case CONNMAN_ELEMENT_TYPE_IPV6:
#if 0
	case CONNMAN_ELEMENT_TYPE_DHCP6:
#endif
		return AF_INET6;
	default:
		connman_error("%s: unknown method %d", __func__,
		    ipconfig->element.type);
		return AF_INET;			/* what? XXX */
	}
}

/**
 * connman_ipconfig_ref:
 * @ipconfig: ipconfig structure
 *
 * Increase the reference count on an ipconfig instance.
 */
struct connman_ipconfig *connman_ipconfig_ref(struct connman_ipconfig *ipconfig)
{
	connman_element_ref(&ipconfig->element);
	return ipconfig;
}

/**
 * connman_ipconfig_unref:
 * @ipconfig: ipconfig structure
 *
 * Decrease the reference count of an ipconfig instance.
 */
void connman_ipconfig_unref(struct connman_ipconfig *ipconfig)
{
	connman_element_unref(&ipconfig->element);
}

/**
 * connman_ipconfig_register:
 * @ipconfig: ipconfig instance
 * @parent: parent element
 *
 * Register the ipconfig instance in the element hierarchy as a child of parent.
 */
int connman_ipconfig_register(struct connman_ipconfig *ipconfig,
    struct connman_element *parent)
{
	_DBG_IPCONFIG("ipconfig %p parent %p", ipconfig, parent);

	__connman_profile_load_ipconfig(ipconfig);

	return connman_element_register(&ipconfig->element, parent);
}

/**
 * connman_ipconfig_unregister:
 * @ipconfig: ipconfig instance
 *
 * Remove the ipconfig instance from the element hierarchy.
 */
void connman_ipconfig_unregister(struct connman_ipconfig *ipconfig)
{
	_DBG_IPCONFIG("ipconfig %p", ipconfig);

	__connman_profile_save_ipconfig(ipconfig);

	connman_element_unregister(&ipconfig->element);
}

struct connman_device  *connman_ipconfig_get_device(struct connman_ipconfig *ipconfig)
{
	struct connman_element *parent;

	parent = ipconfig->element.parent;
	CONNMAN_ASSERT(parent->type == CONNMAN_ELEMENT_TYPE_DEVICE);
	return (struct connman_device *)parent;
}

/**
 * connman_ipconfig_request:
 * @ipconfig: ipconfig instance
 * @tag: matching token (typically parent device object)
 *
 * Call the request method of the ipconfig driver for each ipconfig
 * instance associated with tag.
 */
static void driver_request(struct connman_ipconfig *ipconfig, void *arg)
{
	(void) arg;
	if (ipconfig->driver != NULL && ipconfig->driver->request != NULL)
		ipconfig->driver->request(ipconfig);
}
void connman_ipconfig_request(void *tag)
{
	_DBG_IPCONFIG("tag %p", tag);

	__connman_ipconfig_foreach(tag, driver_request, NULL);
}

/**
 * connman_ipconfig_release:
 * @ipconfig: ipconfig instance
 * @tag: matching token (typically parent device object)
 *
 * Release configuration state.  For each ipconfig instace: remove
 * all children so the associated cleanup work happens (e.g. connections),
 * clear system state, and call the release method of the ipconfig driver.
 * Note the order here is important as, for example, children may call
 * back to us to find configuration state to remove.
 */
static void driver_release(struct connman_ipconfig *ipconfig, void *arg)
{
	int index = connman_ipconfig_get_index(ipconfig);
	(void) arg;

	if (index < 0)
		return;

	/* NB: order is important; children may query ipconfig state */
	connman_element_unregister_children(&ipconfig->element);
	ipconfig->connection = NULL;
	connman_inet_clear_address(index);
	if (ipconfig->resolv != NULL) {
		connman_resolver_unset(ipconfig->resolv);
		ipconfig->resolv = NULL;
	}

	if (ipconfig->driver != NULL && ipconfig->driver->release != NULL)
		ipconfig->driver->release(ipconfig);
	/* clear dynamically configured state so it's not reported */
	connman_ipconfig_clear_address(ipconfig);
}
void connman_ipconfig_release(void *tag)
{
	_DBG_IPCONFIG("tag %p", tag);

	__connman_ipconfig_foreach(tag, driver_release, NULL);
}

/**
 * connman_ipconfig_renew:
 * @ipconfig: ipconfig instance
 * @tag: matching token (typically parent device object)
 *
 * Call the renew method of the ipconfig driver for each ipconfig
 * instance associated with tag.
 */
static void driver_renew(struct connman_ipconfig *ipconfig, void *arg)
{
	(void) arg;
	if (ipconfig->driver != NULL && ipconfig->driver->renew != NULL)
		ipconfig->driver->renew(ipconfig);
}
void connman_ipconfig_renew(void *tag)
{
	_DBG_IPCONFIG("tag %p", tag);

	__connman_ipconfig_foreach(tag, driver_renew, NULL);
}

/**
 * connman_ipconfig_set_error:
 * @ipconfig: ipconfig instance
 * @error: error code
 *
 * Mark an error for the last (request, release, renew) request pushed
 * out to the driver.  The error is sent to the element which causes an
 * external signal.
 */
void connman_ipconfig_set_error(struct connman_ipconfig *ipconfig, int error)
{
	connman_element_set_error(&ipconfig->element, error);
}

/**
 * connman_ipconfig_get_name:
 * @ipconfig: ipconfig structure
 *
 * Get element name
 */
const char *connman_ipconfig_get_name(const struct connman_ipconfig *ipconfig)
{
	return ipconfig->element.name;
}

/**
 * connman_ipconfig_get_path:
 * @ipconfig: ipconfig structure
 *
 * Get element path
 */
const char *connman_ipconfig_get_path(const struct connman_ipconfig *ipconfig)
{
	return ipconfig->element.path;
}

/**
 * connman_ipconfig_get_index:
 * @ipconfig: ipconfig structure
 *
 * Get interface index
 */
int connman_ipconfig_get_index(const struct connman_ipconfig *ipconfig)
{
	return ipconfig->element.index;
}

/**
 * connman_ipconfig_set_index:
 * @ipconfig: ipconfig structure
 * @index: index number
 * @type: device type
 * @ifname: interface name
 *
 * Set index number, type, and interface name of ipconfig
 */
void connman_ipconfig_set_index(struct connman_ipconfig *ipconfig,
				    int index)
{
	ipconfig->element.index = index;
}

/**
 * connman_ipconfig_get_ifname:
 * @ipconfig: ipconfig structure
 *
 * Get interface name
 */
const char *connman_ipconfig_get_ifname(const struct connman_ipconfig *ipconfig)
{
	struct connman_ipdevice *ipdevice;

	if (ipconfig->element.index < 0)
		return NULL;

	ipdevice = ipdevice_lookup(ipconfig->element.index);
	return (ipdevice != NULL ? ipdevice->ifname : NULL);
}

/**
 * connman_ipconfig_get_ipaddress:
 * @ipconfig: ipconfig structure
 *
 * Return the ipaddress associated with the configuration record.
 * Note const'ness of return value.
 */
const struct connman_ipaddress *connman_ipconfig_get_ipaddress(
    const struct connman_ipconfig *ipconfig)
{
	return ipconfig->address;
}

/**
 * connman_ipconfig_add_route:
 * @ipconfig: ipconfig structure
 * @addr: ip address for route
 * @netmask: netmask for route
 * @gateway: gateway address for route
 *
 * Record the route to be installed at bind time.  Return FALSE if
 * no memory is available to record the data.
 */
int connman_ipconfig_add_route(struct connman_ipconfig *ipconfig,
     const char *addr, const char *netmask, const char *gw)
{
	if (iproute_add(&ipconfig->route_list, addr, netmask, gw) == FALSE) {
		connman_error("%s: no memory to add route %s/%s/%s",
		    __func__, addr, netmask, gw);
		return FALSE;
	}
	return TRUE;
}

static void ipconfig_set_dnsproxys(struct connman_ipconfig *ipconfig,
    const struct connman_ipaddress *addr)
{
#define	CONNMAN_IPCONFIG_REQUIRED \
	(CONNMAN_IPCONFIG_GW | CONNMAN_IPCONFIG_DNS | CONNMAN_IPCONFIG_PREFIX)
	const char *ifname = connman_ipconfig_get_ifname(ipconfig);
	struct in_addr gwaddr;
	in_addr_t netmask;
	int i;

	_DBG_IPCONFIG("ifname %s mask 0x%x gateway %s domain_name %s",
	    ifname, addr->mask, addr->gateway, addr->domain_name);

	if ((addr->mask & CONNMAN_IPCONFIG_REQUIRED) !=
	    CONNMAN_IPCONFIG_REQUIRED) {
		_DBG_IPCONFIG("missing gateway, dns, or prefix for %s", ifname);
		if (ipconfig->resolv != NULL) {
			connman_resolver_unset(ipconfig->resolv);
			ipconfig->resolv = NULL;
		}
		return;
	}
	gwaddr.s_addr = inet_addr(addr->gateway);
	/*
	 * For the ">>" operator, the ANSI C standard says that
	 * the result is undefined if the right operand is greater
	 * than equal to the width of the left operand, so we must
	 * handle prefixlen == 32 as a special case.
	 *
	 * TODO(sleffler) fix for IPv6
	 */
	if (addr->prefixlen == 32)
		netmask = 0xffffffff;
	else
		netmask = htonl(~(0xfffffffflu >> addr->prefixlen));

	for (i = 0; addr->dns_servers[i] != NULL; i++) {
		struct in_addr ipaddr;
		/*
		 * Setup host route for the dns server as the proxy code
		 * does SO_BINDTODEVICE on the socket used to fwd requests.
		 */
		ipaddr.s_addr = inet_addr(addr->dns_servers[i]);
		if ((ipaddr.s_addr & netmask) == (gwaddr.s_addr & netmask)) {
			/*
			 * Skip host route if the server is on the local
			 * segment (in which case it's directly reachable).
			 */
			_DBG_IPCONFIG("suppress host route to %s "
			    "(gw %s nm 0x%x)", addr->dns_servers[i],
			    addr->gateway, ntohl(netmask));
		} else {
			connman_inet_add_hostroute(
			    connman_ipconfig_get_index(ipconfig),
			    ipaddr.s_addr, gwaddr.s_addr);
		}
	}
	/*
	 * Add/update resolver entry (NB: after host routes are installed).
	 */
	if (ipconfig->resolv == NULL)
		ipconfig->resolv = connman_resolver_set(ifname,
		    addr->domain_name, addr->search_domains, addr->dns_servers);
	else
		(void) connman_resolver_update(ipconfig->resolv,
		    addr->domain_name, addr->search_domains, addr->dns_servers);
#undef CONNMAN_IPCONFIG_REQUIRED
}

/**
 * connman_ipconfig_bind:
 * @ipconfig: ipconfig structure
 * @ipaddress: ipaddress structure
 *
 * Bind IP address configuration
 */
void connman_ipconfig_bind(struct connman_ipconfig *ipconfig,
    const struct connman_ipaddress *ipaddr)
{
	int index = connman_ipconfig_get_index(ipconfig);

	_DBG_IPCONFIG("mask 0x%x smask 0x%x af %d local %s/%d bcast %s gw %s domain %s mtu %d",
	    ipaddr->mask, ipaddr->smask, ipaddr->af,
	    (ipaddr->mask & CONNMAN_IPCONFIG_LOCAL ? ipaddr->local : NULL),
	    (ipaddr->mask & CONNMAN_IPCONFIG_PREFIX ? ipaddr->prefixlen : -1),
	    (ipaddr->mask & CONNMAN_IPCONFIG_BCAST ? ipaddr->broadcast : NULL),
	    (ipaddr->mask & CONNMAN_IPCONFIG_GW ? ipaddr->gateway : NULL),
	    (ipaddr->mask & CONNMAN_IPCONFIG_DOMAIN ?
	        ipaddr->domain_name : NULL),
	    (ipaddr->mask & CONNMAN_IPCONFIG_MTU ? ipaddr->mtu : -1));

	connman_ipconfig_set_address(ipconfig, ipaddr);

	if (ipaddr != ipconfig->address) {
		/* NB: work below uses updated address data */
		ipaddr = ipconfig->address;
	}

	/* push address state to the interface */
	connman_inet_set_address(index, ipaddr);

	 /* add/update DNS proxy/server state */
	ipconfig_set_dnsproxys(ipconfig, ipaddr);

	/* TODO(sleffler) install routes */
	if (ipconfig->route_list != NULL) {
		const GSList *list = ipconfig->route_list;
		for (; list != NULL; list = list->next) {
			const struct iproute *route = list->data;
			(void) iproute_install(index, route);
		}
	}

	/*
	 * Setup a connection element or update an existing one.
	 * The connection is is how the gateway is installed and
	 * routing state updated.
	 */
	if (ipconfig->connection == NULL) {
		/* NB: record reference for updates */
		ipconfig->connection = __connman_connection_create(ipconfig,
		    &ipconfig->element);
	} else
		connman_element_update(ipconfig->connection);
}

/*
 * IPConfig storage support.
 *
 * Each ipconfig record is uniquiely identified by concatenating
 * the device's identifier (mac address) with a unique token.
 * IPConfig records are referenced in device state with this tag.
 */

static connman_bool_t checkip(int af, const char *str)
{
	struct in6_addr addr;		/* NB: big enough for IPv4+IPv6 */
	return (inet_pton(af, str, &addr) == 1);
}

/*
 * Set an address item and mark it statically-defined.  Return
 * FALSE if the address is invalid wrt the address family; otherwise
 * TRUE. Any previous value is reclaimed.
 */
static connman_bool_t setaddr(struct connman_ipaddress *ipaddr, int which,
    char *str)
{
	if (!checkip(ipaddr->af, str)) {
		connman_error("%s: invalid address \"%s\" (af %d)",
		    __func__, str, ipaddr->af);
		goto bad;
	}
	switch (which) {
	case CONNMAN_IPCONFIG_LOCAL:
		g_free(ipaddr->local);
		ipaddr->local = str;
		break;
	case CONNMAN_IPCONFIG_PEER:
		g_free(ipaddr->peer);
		ipaddr->peer = str;
		break;
	case CONNMAN_IPCONFIG_BCAST:
		g_free(ipaddr->broadcast);
		ipaddr->broadcast = str;
		break;
	case CONNMAN_IPCONFIG_GW:
		g_free(ipaddr->gateway);
		ipaddr->gateway = str;
		break;
	default:
		connman_error("%s: unknown address type %d for \"%s\"",
		    __func__, which, str);
		goto bad;
	}
	ipaddr->mask |= which;
	ipaddr->smask |= which;
	return TRUE;
bad:
	g_free(str);
	return FALSE;
}

/*
 * Set an address based on any value retrieved from the key file.
 */
static void setip(GKeyFile *keyfile, const char *key,
    const char *tag, struct connman_ipaddress *ipaddr, int which)
{
	char *str;

	str = g_key_file_get_string(keyfile, key, tag, NULL);
	if (str == NULL)
		return;
	if (!setaddr(ipaddr, which, str))
		connman_error("ipconfig_load: invalid %s \"%s\"", tag, str);
}

/*
 * Set the DNS servers by splitting a comma-separated list.
 * The data is marked statically-defined.  Any previous value
 * is reclaimed.
 */
static void setdns(struct connman_ipaddress *ipaddr, const char *str)
{
	g_strfreev(ipaddr->dns_servers);
	/* list of servers is a ,-separated list */
	ipaddr->dns_servers = g_strsplit(str, ",", 0);
	ipaddr->mask |= CONNMAN_IPCONFIG_DNS;
	ipaddr->smask |= CONNMAN_IPCONFIG_DNS;
}

/*
 * Set the DNS search domains by splitting a comma-separated list.
 * The data is marked statically-defined.  Any previous value
 * is reclaimed.
 */
static void setdomains(struct connman_ipaddress *ipaddr, const char *str)
{
	connman_info("setdomains(%s)", str);

	g_strfreev(ipaddr->search_domains);
	/* list of domains is a ,-separated list */
	ipaddr->search_domains = g_strsplit(str, ",", 0);
	ipaddr->mask |= CONNMAN_IPCONFIG_SEARCH;
	ipaddr->smask |= CONNMAN_IPCONFIG_SEARCH;
}

static int ipconfig_storage_load(struct connman_ipconfig *ipconfig,
    GKeyFile *keyfile)
{
	gchar *key;
	struct connman_ipaddress *ipaddr = ipconfig->address;
	GError *error;
	char *str;
	gint val;
	int err = 0;

	_DBG_IPCONFIG("ipconfig %p", ipconfig);

	key = g_strdup_printf("ipconfig_%s", ipconfig->element.name);
	if (key == NULL) {
		connman_error("%s: no memory for identifier", __func__);
		err = -ENOMEM;
		goto done;
	}

	if (g_key_file_has_group(keyfile, key) == FALSE) {
		err = -ESRCH;
		goto done;
	}

	/* NB: connman_ipconfig_create sets the type */

	error = NULL;
	val = g_key_file_get_integer(keyfile, key, "Prefixlen", &error);
	if (error == NULL) {
		if (!(0 <= val && val <= MAX_PREFIXLEN)) {
			connman_error("%s: %s", __func__, error->message);
			goto done;
		}
		ipaddr->prefixlen = (unsigned char) val;
		ipaddr->mask |= CONNMAN_IPCONFIG_PREFIX;
		ipaddr->smask |= CONNMAN_IPCONFIG_PREFIX;
	}

	error = NULL;
	val = g_key_file_get_integer(keyfile, key, "Mtu", &error);
	if (error == NULL) {
		ipaddr->mtu = val;
		ipaddr->mask |= CONNMAN_IPCONFIG_MTU;
		ipaddr->smask |= CONNMAN_IPCONFIG_MTU;
	}

	setip(keyfile, key, "Address", ipaddr, CONNMAN_IPCONFIG_LOCAL);
	setip(keyfile, key, "Broadcast", ipaddr, CONNMAN_IPCONFIG_BCAST);
	setip(keyfile, key, "PeerAddress", ipaddr, CONNMAN_IPCONFIG_PEER);
	setip(keyfile, key, "Gateway", ipaddr, CONNMAN_IPCONFIG_GW);
	str = g_key_file_get_string(keyfile, key, "DomainName", NULL);
	if (str != NULL) {
		g_free(ipaddr->domain_name);
		ipaddr->domain_name = str;
		ipaddr->mask |= CONNMAN_IPCONFIG_DOMAIN;
		ipaddr->smask |= CONNMAN_IPCONFIG_DOMAIN;
	}
	str = g_key_file_get_string(keyfile, key, "NameServers", NULL);
	if (str != NULL) {
		setdns(ipaddr, str);
		g_free(str);
	}
	str = g_key_file_get_string(keyfile, key, "SearchDomains", NULL);
	if (str != NULL) {
		setdomains(ipaddr, str);
		g_free(str);
	}
	/* TODO(sleffler) do consistency checking */
done:
	g_free(key);

	return err;
}

struct ipconfig_paths {
	const char const **names;
	int maxnames;
	int ix;
};
static void enumerate_ipconfig(struct connman_ipconfig *ipconfig, void *arg)
{
	struct ipconfig_paths *paths = arg;

	if (paths->ix < paths->maxnames) {
		/* NB: need Method here so it's available at load time */
		paths->names[paths->ix++] = g_strdup_printf("%s:%s",
		    connman_ipconfig_get_name(ipconfig),
		    __connman_ipconfig_get_method(ipconfig));
	}
}

void __connman_ipconfig_get_keys(struct connman_device *device,
    int maxnames, const char const *names[], int *count)
{
	struct ipconfig_paths paths;

	paths.names = names;
	paths.maxnames = maxnames;
	paths.ix = 0;
	__connman_ipconfig_foreach(device, enumerate_ipconfig, &paths);
	*count = paths.ix;
}

static void set_string(GKeyFile *keyfile, gchar *key,
    const struct connman_ipaddress *ipaddr, int mask,
    const char *name, const char *value)
{
	if (ipaddr->smask & mask)
		g_key_file_set_string(keyfile, key, name, value);
	else
		g_key_file_remove_key(keyfile, key, name, NULL);
}

static void set_integer(GKeyFile *keyfile, gchar *key,
    const struct connman_ipaddress *ipaddr, int mask,
    const char *name, int value)
{
	if (ipaddr->smask & mask)
		g_key_file_set_integer(keyfile, key, name, value);
	else
		g_key_file_remove_key(keyfile, key, name, NULL);
}

static int ipconfig_storage_save(const struct connman_ipconfig *ipconfig,
    GKeyFile *keyfile)
{
	gchar *key;
	const struct connman_ipaddress *ipaddr =
	    connman_ipconfig_get_ipaddress(ipconfig);

	_DBG_IPCONFIG("ipconfig %p", ipconfig);

	key = g_strdup_printf("ipconfig_%s", ipconfig->element.name);
	if (key == NULL) {
		connman_error("%s: no memory for identifier", __func__);
		goto done;
	}

	/* TODO(sleffler) wrong type */
	g_key_file_set_string(keyfile, key, "Method",
	    method2str(ipconfig->element.type));

	/* NB: save only statically-defined values */
	set_string(keyfile, key, ipaddr, CONNMAN_IPCONFIG_LOCAL,
	    "Address", ipaddr->local);
	set_integer(keyfile, key, ipaddr, CONNMAN_IPCONFIG_PREFIX,
	    "Prefixlen", ipaddr->prefixlen);
	set_integer(keyfile, key, ipaddr, CONNMAN_IPCONFIG_MTU,
	    "Mtu", ipaddr->mtu);
	set_string(keyfile, key, ipaddr, CONNMAN_IPCONFIG_BCAST,
	    "Broadcast", ipaddr->broadcast);
	set_string(keyfile, key, ipaddr, CONNMAN_IPCONFIG_PEER,
	    "PeerAddress", ipaddr->peer);
	set_string(keyfile, key, ipaddr, CONNMAN_IPCONFIG_GW,
	    "Gateway", ipaddr->gateway);
	set_string(keyfile, key, ipaddr, CONNMAN_IPCONFIG_DOMAIN,
	    "DomainName", ipaddr->domain_name);
	if (ipaddr->smask & CONNMAN_IPCONFIG_DNS) {
		gchar *dns = g_strjoinv(",", ipaddr->dns_servers);
		g_key_file_set_string(keyfile, key, "NameServers", dns);
		g_free(dns);
	} else {
		g_key_file_remove_key(keyfile, key, "NameServers", NULL);
	}
	if (ipaddr->smask & CONNMAN_IPCONFIG_SEARCH) {
		gchar *domains = g_strjoinv(",", ipaddr->search_domains);
		g_key_file_set_string(keyfile, key, "SearchDomains", domains);
		g_free(domains);
	} else {
		g_key_file_remove_key(keyfile, key, "SearchDomains", NULL);
	}
done:
	g_free(key);
	return 0;
}

static int ipconfig_storage_remove(const struct connman_ipconfig *ipconfig)
{
	const struct connman_storage_ident *ident =
	    __connman_profile_active_ident();
	GKeyFile *keyfile;
	gchar *group;

	_DBG_IPCONFIG("ipconfig %p ident %p", ipconfig, ident);

	if (ident == NULL)
		return -EINVAL;

	keyfile = __connman_storage_open(ident);
	if (keyfile == NULL)
		return 0;

	group = g_strdup_printf("ipconfig_%s", ipconfig->element.name);
	if (group == NULL) {
		connman_error("%s: no memory for group", __func__);
		goto done;
	}
	(void) g_key_file_remove_group(keyfile, group, NULL);
	g_free(group);
done:
	__connman_storage_close(ident, keyfile, TRUE);
	return 0;
}

static struct connman_storage ipconfig_storage = {
	.name		= "ipconfig",
	.priority	= CONNMAN_STORAGE_PRIORITY_LOW,
	.ipconfig_load	= ipconfig_storage_load,
	.ipconfig_save	= ipconfig_storage_save,
};

/*
 * D-bus interface support.
 */

static void append_strv(DBusMessageIter *dict, void *arg)
{
	char **strv = arg;
	int i;

	for (i = 0; strv[i] != NULL; i++)
		dbus_message_iter_append_basic(dict, DBUS_TYPE_STRING, &strv[i]);
}

static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg,
    void *data)
{
	struct connman_ipconfig *ipconfig = data;
	DBusMessage *reply;
	DBusMessageIter array, dict;
	const struct connman_ipaddress *ipaddr;
	const char *str;
	int mask;

	_DBG_IPCONFIG("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
		return __connman_error_permission_denied(msg);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_iter_init_append(reply, &array);

	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	/* TODO(sleffler) wrong type */
	str = method2str(ipconfig->element.type);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Method",
		    DBUS_TYPE_STRING, &str);

	ipaddr = ipconfig->address;
	mask = ipaddr->mask | ipaddr->smask;
	if (mask & CONNMAN_IPCONFIG_LOCAL)
		connman_dbus_dict_append_variant(&dict, "Address",
		    DBUS_TYPE_STRING, &ipaddr->local);
	if (mask & CONNMAN_IPCONFIG_MTU)
		connman_dbus_dict_append_variant(&dict, "Mtu",
		    DBUS_TYPE_INT32, &ipaddr->mtu);
	if (mask & CONNMAN_IPCONFIG_PREFIX) {
		int prefixlen = ipaddr->prefixlen;
		connman_dbus_dict_append_variant(&dict, "Prefixlen",
		    DBUS_TYPE_INT32, &prefixlen);
	}
	if (mask & CONNMAN_IPCONFIG_BCAST)
		connman_dbus_dict_append_variant(&dict, "Broadcast",
		    DBUS_TYPE_STRING, &ipaddr->broadcast);
	if (mask & CONNMAN_IPCONFIG_PEER)
		connman_dbus_dict_append_variant(&dict, "PeerAddress",
		    DBUS_TYPE_STRING, &ipaddr->peer);
	if (mask & CONNMAN_IPCONFIG_GW)
		connman_dbus_dict_append_variant(&dict, "Gateway",
		    DBUS_TYPE_STRING, &ipaddr->gateway);
	if (mask & CONNMAN_IPCONFIG_DOMAIN)
		connman_dbus_dict_append_variant(&dict, "DomainName",
		    DBUS_TYPE_STRING, &ipaddr->domain_name);
	if (mask & CONNMAN_IPCONFIG_DNS)
		connman_dbus_dict_append_variant_array(&dict,
		    "NameServers", DBUS_TYPE_STRING,
		    append_strv, ipaddr->dns_servers);
	if (ipaddr->mask & CONNMAN_IPCONFIG_SEARCH)
		connman_dbus_dict_append_variant_array(&dict,
		    "SearchDomains", DBUS_TYPE_STRING,
		    append_strv, ipaddr->search_domains);

	dbus_message_iter_close_container(&array, &dict);

	return reply;
}

static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg,
    void *data)
{
	struct connman_ipconfig *ipconfig = data;
	struct connman_ipaddress *ipaddr = ipconfig->address;
	DBusMessageIter iter, value;
	const char *key, *str;
	int type;

	_DBG_IPCONFIG("conn %p", conn);

	if (dbus_message_iter_init(msg, &iter) == FALSE)
		return __connman_error_invalid_arguments(msg);

	dbus_message_iter_get_basic(&iter, &key);
	dbus_message_iter_next(&iter);
	dbus_message_iter_recurse(&iter, &value);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	type = dbus_message_iter_get_arg_type(&value);

	if (g_str_equal(key, "Address") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &str);
		if (!setaddr(ipaddr, CONNMAN_IPCONFIG_LOCAL, g_strdup(str)))
			return __connman_error_invalid_arguments(msg);
	} else if (g_str_equal(key, "Prefixlen") == TRUE) {
		int prefixlen;

		if (type != DBUS_TYPE_INT32)
			return __connman_error_invalid_arguments(msg);
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_not_supported(msg);

		dbus_message_iter_get_basic(&value, &prefixlen);
		if (!(0 <= prefixlen && prefixlen <= MAX_PREFIXLEN))
			return __connman_error_invalid_arguments(msg);

		ipaddr->prefixlen = (unsigned char) prefixlen;
		ipaddr->mask |= CONNMAN_IPCONFIG_PREFIX;
		ipaddr->smask |= CONNMAN_IPCONFIG_PREFIX;
	} else if (g_str_equal(key, "Mtu") == TRUE) {
		int mtu;

		if (type != DBUS_TYPE_INT32)
			return __connman_error_invalid_arguments(msg);
		/* NB: can override dynamic value */

		dbus_message_iter_get_basic(&value, &mtu);
		/* TODO(sleffler) can we sanity check? */
		if (mtu < 576)
			return __connman_error_invalid_arguments(msg);

		ipaddr->mtu = mtu;
		ipaddr->mask |= CONNMAN_IPCONFIG_MTU;
		ipaddr->smask |= CONNMAN_IPCONFIG_MTU;
	} else if (g_str_equal(key, "Broadcast") == TRUE) {
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_not_supported(msg);

		dbus_message_iter_get_basic(&value, &str);
		if (!setaddr(ipaddr, CONNMAN_IPCONFIG_BCAST, g_strdup(str)))
			return __connman_error_invalid_arguments(msg);
	} else if (g_str_equal(key, "PeerAddress") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_not_supported(msg);

		dbus_message_iter_get_basic(&value, &str);
		if (!setaddr(ipaddr, CONNMAN_IPCONFIG_PEER, g_strdup(str)))
			return __connman_error_invalid_arguments(msg);
	} else if (g_str_equal(key, "Gateway") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_not_supported(msg);

		dbus_message_iter_get_basic(&value, &str);
		if (!setaddr(ipaddr, CONNMAN_IPCONFIG_GW, g_strdup(str)))
			return __connman_error_invalid_arguments(msg);
	} else if (g_str_equal(key, "DomainName") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &str);

		g_free(ipaddr->domain_name);
		ipaddr->domain_name = g_strdup(str);
		ipaddr->mask |= CONNMAN_IPCONFIG_DOMAIN;
		ipaddr->smask |= CONNMAN_IPCONFIG_DOMAIN;
	} else if (g_str_equal(key, "NameServers") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		/*
		 * TODO(sleffler) use array of str's instead of
		 * comma-delimited str
		 */
		dbus_message_iter_get_basic(&value, &str);
                setdns(ipaddr, str);
	} else if (g_str_equal(key, "SearchDomains") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (!ipconfig_isstatic(ipconfig))
			return __connman_error_not_supported(msg);
		dbus_message_iter_get_basic(&value, &str);
		setdomains(ipaddr, str);
	} else
		return __connman_error_invalid_property(msg);

	__connman_profile_save_ipconfig(ipconfig);

	connman_element_update(&ipconfig->element);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *clear_property(DBusConnection *conn, DBusMessage *msg,
    void *data)
{
	struct connman_ipconfig *ipconfig = data;
	struct connman_ipaddress *ipaddr = ipconfig->address;
	const char *name;
	int mask;

	_DBG_IPCONFIG("conn %p", conn);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
							DBUS_TYPE_INVALID);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	if (g_str_equal(name, "Address") == TRUE)
		mask = CONNMAN_IPCONFIG_LOCAL;
	else if (g_str_equal(name, "Prefixlen") == TRUE)
		mask = CONNMAN_IPCONFIG_PREFIX;
	else if (g_str_equal(name, "Mtu") == TRUE)
		mask = CONNMAN_IPCONFIG_MTU;
	else if (g_str_equal(name, "Broadcast") == TRUE)
		mask = CONNMAN_IPCONFIG_BCAST;
	else if (g_str_equal(name, "PeerAddress") == TRUE)
		mask = CONNMAN_IPCONFIG_PEER;
	else if (g_str_equal(name, "Gateway") == TRUE)
		mask = CONNMAN_IPCONFIG_GW;
	else if (g_str_equal(name, "DomainName") == TRUE)
		mask = CONNMAN_IPCONFIG_DOMAIN;
	else if (g_str_equal(name, "NameServers") == TRUE)
		mask = CONNMAN_IPCONFIG_DNS;
	else
		return __connman_error_invalid_property(msg);

	ipaddr->smask &= ~mask;
	/* TODO(sleffler) should we leave current state alone? */
	/* NB: no way to record dynamic state (e.g. DHCP dns config) */
	ipaddress_clear(ipaddr, mask);
	__connman_profile_save_ipconfig(ipconfig);

	connman_element_update(&ipconfig->element);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *remove_ipconfig(DBusConnection *conn, DBusMessage *msg,
    void *data)
{
	struct connman_ipconfig *ipconfig = data;
	struct connman_device *device;

	_DBG_IPCONFIG("conn %p", conn);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	device = connman_ipconfig_get_device(ipconfig);
	/* TODO(sleffler) expand storage api? */
	/* NB: must be done before in-memory state is reclaimed */
	ipconfig_storage_remove(ipconfig);
	__connman_device_remove_ipconfig(device, ipconfig);
	__connman_profile_save_device(device);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *move_ipconfig(struct connman_ipconfig *ipconfig,
    DBusMessage *msg, connman_bool_t before)
{
	struct connman_ipconfig *target;
	struct connman_device *device;
	const char *path;

	_DBG_IPCONFIG("ipconfig %p %s", ipconfig, before ? "before" : "after");

	dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	target = ipconfig_find(path);
	if (target == NULL || target == ipconfig)
		return __connman_error_invalid_ipconfig(msg);

	_DBG_IPCONFIG("target %p", target);

	device = connman_ipconfig_get_device(ipconfig);
	ipconfig_move(ipconfig, target, before ? 0 : 1);
	__connman_profile_save_device(device);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *move_before(DBusConnection *conn, DBusMessage *msg,
    void *user_data)
{
	struct connman_ipconfig *ipconfig = user_data;

	return move_ipconfig(ipconfig, msg, TRUE);
}

static DBusMessage *move_after(DBusConnection *conn, DBusMessage *msg,
    void *user_data)
{
	struct connman_ipconfig *ipconfig = user_data;

	return move_ipconfig(ipconfig, msg, FALSE);
}

static GDBusMethodTable ipconfig_methods[] = {
	{ "GetProperties", "",      "a{sv}", get_properties  },
	{ "SetProperty",   "sv",    "",      set_property,
						G_DBUS_METHOD_FLAG_ASYNC },
	{ "ClearProperty", "s",  "",         clear_property  },
	{ "Remove",        "",      "",      remove_ipconfig },
	{ "MoveBefore",    "o",     "",      move_before     },
	{ "MoveAfter",     "o",     "",      move_after      },
	{ },
};

static GDBusSignalTable ipconfig_signals[] = {
	{ "PropertyChanged", "sv" },
	{ },
};

static int driver_probe(struct connman_element *element)
{
	if (g_dbus_register_interface(connection, element->path,
					CONNMAN_IPCONFIG_INTERFACE,
					ipconfig_methods, ipconfig_signals,
					NULL, element, NULL) == FALSE) {
		connman_error("Failed to register %s ipconfig", element->path);
		return -EIO;
	}
	return 0;
}

static void driver_remove(struct connman_element *element)
{
	g_dbus_unregister_interface(connection, element->path,
	    CONNMAN_IPCONFIG_INTERFACE);
}

static void driver_update(struct connman_element *element)
{
	struct connman_ipconfig *ipconfig = (struct connman_ipconfig *) element;
	int index = connman_ipconfig_get_index(ipconfig);
	struct connman_ipaddress *ipaddr = ipconfig->address;

	/* push address state to the interface */
	connman_inet_set_address(index, ipaddr);

	 /* update DNS proxy/server state */
	ipconfig_set_dnsproxys(ipconfig, ipaddr);

	/* NB: connection is updated because it is a child element */
}

static struct connman_driver dhcp_driver = {
	.name		= "ipconfig-dhcp",
	.type		= CONNMAN_ELEMENT_TYPE_DHCP,
	.priority	= CONNMAN_DRIVER_PRIORITY_LOW,
	.probe		= driver_probe,
	.remove		= driver_remove,
	.update		= driver_update,
};

static struct connman_driver ppp_driver = {
	.name		= "ipconfig-ppp",
	.type		= CONNMAN_ELEMENT_TYPE_PPP,
	.priority	= CONNMAN_DRIVER_PRIORITY_LOW,
	.probe		= driver_probe,
	.remove		= driver_remove,
	.update		= driver_update,
};

static struct connman_driver ipv4_driver = {
	.name		= "ipconfig-ipv4",
	.type		= CONNMAN_ELEMENT_TYPE_IPV4,
	.priority	= CONNMAN_DRIVER_PRIORITY_LOW,
	.probe		= driver_probe,
	.remove		= driver_remove,
	.update		= driver_update,
};

static struct connman_driver ipv6_driver = {
	.name		= "ipconfig-ipv6",
	.type		= CONNMAN_ELEMENT_TYPE_IPV6,
	.priority	= CONNMAN_DRIVER_PRIORITY_LOW,
	.probe		= driver_probe,
	.remove		= driver_remove,
	.update		= driver_update,
};

int __connman_ipconfig_init(void)
{
	connection = connman_dbus_get_connection();
	if (connection == NULL)
		return -EIO;
	(void) connman_driver_register(&dhcp_driver);
	(void) connman_driver_register(&ppp_driver);
	(void) connman_driver_register(&ipv4_driver);
	(void) connman_driver_register(&ipv6_driver);
	/* XXX check return value */
	connman_storage_register(&ipconfig_storage);
	ipdevice_init();
	return 0;
}

void __connman_ipconfig_cleanup(void)
{
	ipdevice_cleanup();
	connman_storage_unregister(&ipconfig_storage);
	connman_driver_unregister(&dhcp_driver);
	connman_driver_unregister(&ppp_driver);
	connman_driver_unregister(&ipv4_driver);
	connman_driver_unregister(&ipv6_driver);
	dbus_connection_unref(connection);
}

/*
 * IPConfig driver support.
 */
static GSList *driver_list = NULL;

static gint compare_priority(gconstpointer a, gconstpointer b)
{
	const struct connman_ipconfig_driver *driver1 = a;
	const struct connman_ipconfig_driver *driver2 = b;

	return driver2->priority - driver1->priority;
}

/**
 * connman_ipconfig_driver_register:
 * @driver: IP configuration driver
 *
 * Register a new IP configuration driver
 *
 * Returns: %0 on success
 */
int connman_ipconfig_driver_register(
    const struct connman_ipconfig_driver *driver)
{
	_DBG_IPCONFIG("driver %s(%p) type %s(%d) priority %d",
	    driver->name, driver, method2str(driver->type), driver->type,
	    driver->priority);

	/* TODO(sleffler) __DECONST */
	driver_list = g_slist_insert_sorted(driver_list, (void *) driver,
							compare_priority);
	return 0;
}

static void clear_driver_ref(struct connman_ipconfig *ipconfig, void *arg)
{
	if (ipconfig->driver == arg)
		ipconfig->driver = NULL;
}

/**
 * connman_ipconfig_driver_unregister:
 * @driver: IP configuration driver
 *
 * Remove a previously registered IP configuration driver.
 */
void connman_ipconfig_driver_unregister(
    const struct connman_ipconfig_driver *driver)
{
	_DBG_IPCONFIG("driver %s(%p) type %s(%d) priority %d",
	    driver->name, driver, method2str(driver->type), driver->type,
	    driver->priority);

	/* walk list of ipconfig's and invalidate any references */
	__connman_ipconfig_foreach(NULL, clear_driver_ref, (void *) driver);

	driver_list = g_slist_remove(driver_list, driver);
}

static const struct connman_ipconfig_driver *ipconfig_driver_lookup(
    enum connman_ipconfig_type type)
{
	GSList *list;

	for (list = driver_list; list != NULL; list = list->next) {
		const struct connman_ipconfig_driver *driver = list->data;
		if (driver->type == type)
			return driver;
	}
	return NULL;
}
